#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#include "net.h"
#include "tools.h"

int	xx_nonblock_socket(int sockfd)
{
	int	mask;

	if ( (mask = fcntl(sockfd, F_GETFL, 0)) < 0 || fcntl(sockfd, F_SETFL, mask | O_NONBLOCK) < 0) {
		return -1;
	}

	return 0;
}

// both block and nonblock
int xx_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int nsec)
{
	int				ret	  = 0;
	int				error = 0;
	socklen_t		len   = 0;
	fd_set			rset, wset;
	struct timeval	tval;

	if ( NULL == addr ) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}
	error = 0;
	if ( (ret = connect(sockfd, addr, addrlen)) < 0)
		if (errno != EINPROGRESS && errno != EINTR )
			return(-1);

	/* Do whatever we want while the connect is taking place. */

	if (ret == 0)
		goto done;	/* connect completed immediately */

	FD_ZERO(&rset);
	FD_SET(sockfd, &rset);
	wset = rset;
	tval.tv_sec = nsec;
	tval.tv_usec = 0;

	if ( (ret = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) {
		close(sockfd);		/* timeout */
		errno = ETIMEDOUT;
		return(-1);
	}

	if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
		len = sizeof(error);
		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
			return(-1);			/* Solaris pending error */
	} else{
		close(sockfd);
		return -1;
	}
done:
	if (error) {
		close(sockfd);		/* just in case */
		errno = error;
		return(-1);
	}

	return(0);
}

int xx_connect_host(const char * const host, const char * const port, const int type, const struct sockaddr *addr)
{
	int					ret    = 0; 
	int					sockfd = 0;
	/*int				 optval = 0*/;

	if ( NULL == host || NULL == port || NULL == addr ) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	if ( type ) {
		xx_dbg("Input para Invalid\n");
		return -1;
	}
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if ( sockfd <= 0 ) {
		perror("socket");
		exit(-1);
	}

//	optval = 1;
//	if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0 ) {
//		return -1;
//	}

	if ( xx_nonblock_socket(sockfd) < 0 ) {
		close(sockfd);
		return -1;
	}

	if ( addr ) {
		ret = xx_connect(sockfd, addr, sizeof(struct sockaddr), 0);
		return ret;
	}

	struct addrinfo		hints, *host_info = NULL, *temp_info = NULL;	

	bzero(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = 0;

	ret = getaddrinfo(host, port, &hints, &host_info);
	if ( ret != 0 ) {
		xx_err("getaddrinfo: %s\n", gai_strerror(ret));
		exit(-1);
	}

	temp_info = host_info;
	while ( host_info ){
//		xx_dbg("connecting to %s:%s\n", host, port);  
		ret = xx_connect(sockfd, host_info->ai_addr, host_info->ai_addrlen, 0);
		if ( ret < 0 ) {
			host_info = host_info->ai_next;
			continue;
		}
		xx_dbg("connectted to %s\n", inet_ntoa(((struct sockaddr_in *)(host_info->ai_addr))->sin_addr));  
		break;
	}

	if ( temp_info ) {
		freeaddrinfo(temp_info);
	}

	if ( host_info == NULL ) {
//		xx_err("Can't connect to %s:%s\n", host, port);
		close(sockfd);
		return -1;
	}

	return sockfd;
}

int xx_listen_port(int *listenPort)
{
	int				ret, listenFd, optval;
	struct sockaddr_in		host_addr;

	if ( NULL == listenPort ) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	memset(&host_addr, 0, sizeof(host_addr));

	host_addr.sin_family = AF_INET;
	host_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	host_addr.sin_port = htons(*listenPort);

	listenFd = socket(AF_INET, SOCK_STREAM, 0);
	if ( listenFd < 0 ) {
		xx_err("socket failed\n");
		return -1;
	}

	optval = 1;
	if ( setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0 ) {
		return -1;
	}

	if ( xx_nonblock_socket(listenFd) < 0 ) {
		return -1;
	}

	ret = bind(listenFd, (struct sockaddr *)&host_addr, sizeof(host_addr));
	if ( ret < 0 ) {
		xx_err("bind failed\n");
		return -1;
	}

	ret = listen(listenFd, LISTEN_BACK_LOG);
	if ( ret < 0 ) {
		xx_err("listen failed\n");
		return -1;
	}

	xx_dbg("listen on port: %d\n", *listenPort);

	return listenFd;
}

int xx_readn(int sockfd, char *buf, int bufLen)
{
	int			n    = 0;
	int			left = 0;

	if ( NULL == buf ) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	if ( sockfd <= 0 ) {
		xx_assert(0);
		xx_dbg("Input para Invalid\n");
		return -1;
	}

	left = bufLen;
	while ( left > 0 ){
		n = read(sockfd, buf + bufLen - left, left);
		if ( n == 0){ 
			xx_dbg("read got EOF\n");
			break;
		}
		else if ( n < 0 ) {
			perror("read");
//			xx_err("read error\n");
			if ( errno == EINTR ) {
				continue;
			}
			else if ( errno == EAGAIN || errno == EWOULDBLOCK ) {
				break;
			}
			else {
				close(sockfd);
				return -1;
			}
		}
		left -= n;
	}

//	if ( n != 0 ) {
//		xx_err("buf len isn't enought\n");
//		return -1;
//	}

	return (bufLen - left);
}

int xx_writen(int sockfd, char *buf, int bufLen)
{
	int			left = 0;
	int			n	 = 0;

	if ( NULL == buf ) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	left = bufLen;
	while ( left > 0 ){
		n = write(sockfd, buf + bufLen - left, left );
		if ( n < 0 /* && EPIPE == errno */) {
			perror("write");
			if ( errno == EINTR ) {
				continue;
			}else if ( errno == EAGAIN || errno == EWOULDBLOCK ) {
				return 0;
			}else{
//				xx_err("write error\n");
				close(sockfd);
				return -1;
			}
		}

		left -= n; 
	}

	xx_dbg("%d byts writen\n", bufLen - left);
	return (bufLen - left);
}

int xx_net_init()
{


	return 0;
}
